using FI.Core.Utilities;
// ==================================================================================================
// Copyright(C) 2005 FI Ltd. All rights reserved.
//
// SYSTEM NAME	: FI.Net Application Framework
// COMPONENT ID	: FI.Common
// COMPONENT DESC:		
//
// CREATED DATE/BY:		18 Aug 2018 / Tian Mi
//
// REVISION HISTORY:	
// DATE/BY			SR#/CS/PM#/OTHERS		DESCRIPTION OF CHANGE
// ==================================================================================================

using System;
using System.Reflection;
using System.Runtime.Serialization;

namespace FI.Core
{
    /// <summary>
    ///  Represents fault detail information.
    /// </summary>
    [DataContract(Namespace = Constants.Namespace)]
    public class ServiceExceptionDetail
    {
        #region Properties
        /// <summary>
        /// The namespace of SubCode for the <see cref="ServiceExceptionDetail"/> based fault.
        /// </summary>
        public readonly static string FaultSubCodeNamespace = string.Format("{0}{1}/", Constants.Namespace, "exceptionhandling");

        /// <summary>
        /// The name of SubCode for the <see cref="ServiceExceptionDetail"/> based fault.
        /// </summary>
        public readonly static string FaultSubCodename = "ServiceError";

        /// <summary>
        /// Gets or sets the help link.
        /// </summary>
        /// <value>The help link.</value>
        [DataMember]
        public string HelpLink { get; set; }
        /// <summary>
        /// Gets or sets the inner exception.
        /// </summary>
        /// <value>The inner exception.</value>
        [DataMember]
        public ServiceExceptionDetail InnerException { get; set; }
        /// <summary>
        /// Gets or sets the message.
        /// </summary>
        /// <value>The message.</value>
        [DataMember]
        public string Message { get; set; }
        /// <summary>
        /// Gets or sets the stack trace.
        /// </summary>
        /// <value>The stack trace.</value>
        [DataMember]
        public string StackTrace { get; set; }
        /// <summary>
        /// Gets or sets the type.
        /// </summary>
        /// <value>The type.</value>
        [DataMember]
        public string TypeAssemblyQualifiedName { get; set; }

        /// <summary>
        /// HandingInstanceId for exception handling.
        /// </summary>
        [DataMember]
        public Guid HandingInstanceId { get; set; }

        /// <summary>
        /// Gets the type of the exception.
        /// </summary>
        /// <value>The type of the exception.</value>
        public Type ExceptionType
        {
            get
            {
                Guard.ArgumentNotNullOrEmpty(this.TypeAssemblyQualifiedName, "TypeAssemblyQualifiedName");
                return Type.GetType(this.TypeAssemblyQualifiedName);
            }
        }


        #endregion

        #region Constructors
        /// <summary>
        /// Creates a new instance of ServiceException class with the raised exception information
        /// </summary>
        /// <param name="ex">The raised exception</param>
        public ServiceExceptionDetail(Exception ex)
        {
            Guard.ArgumentNotNull(ex, "ex");
            ConstructorInfo constructor = ex.GetType().GetConstructor(new Type[] { typeof(string), typeof(Exception) });
            if (constructor == null)
            {
                this.TypeAssemblyQualifiedName = typeof(ServiceInvocationException).AssemblyQualifiedName;
            }
            else
            {
                this.TypeAssemblyQualifiedName = ex.GetType().AssemblyQualifiedName;
            }

            this.HelpLink = ex.HelpLink;
            this.Message = ex.Message;
            this.StackTrace = ex.StackTrace;

            if (ex.InnerException != null)
            {
                InnerException = new ServiceExceptionDetail(ex.InnerException);
            }
        }
        #endregion

        #region static Methods
        /// <summary>
        /// Gets the fault action.
        /// </summary>
        /// <param name="contractNamespace">The contract namespace.</param>
        /// <param name="operationName">Name of the operation.</param>
        /// <returns></returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object,System.Object)")]
        public static string GetFaultAction(string contractNamespace, string operationName)
        {
            contractNamespace = contractNamespace??"";
            operationName = operationName ?? "";
            return string.Format("{0}/{1}_ServiceExceptionDetail", contractNamespace.TrimEnd("/".ToCharArray()), operationName);
        }

        /// <summary>
        /// Builds the exception detail.
        /// </summary>
        /// <param name="exception">The exception.</param>
        /// <param name="includeInnerException">if set to <c>true</c> [include inner exception].</param>
        /// <param name="includeStackTrace">if set to <c>true</c> [include stack trace].</param>
        /// <returns></returns>
        public static ServiceExceptionDetail BuildExceptionDetail(Exception exception, bool includeInnerException, bool includeStackTrace)
        {
            Guard.ArgumentNotNull(exception, "exception");
            ServiceExceptionDetail exceptionDetail = new ServiceExceptionDetail(exception);
            if (!includeInnerException)
            {
                exceptionDetail.InnerException = null;
            }

            if (!includeStackTrace)
            {
                TrimStackTrace(exceptionDetail);
            }

            return exceptionDetail;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Concerve to CLR <see cref="Exception"/>.
        /// </summary>
        /// <returns>The CLR <see cref="Exception"/> converted.</returns>
        public Exception ToException()
        {
            Exception exception = Activator.CreateInstance(this.ExceptionType, true) as Exception;
            if (null == exception)
            {
                exception = new ServiceInvocationException();
            }

            SetExceptionField(exception, "_message", this.Message);
            SetExceptionField(exception, "_stackTraceString", this.StackTrace);
            SetExceptionField(exception, "_helpURL", this.HelpLink);

            if (null != this.InnerException)
            {
                SetExceptionField(exception, "_innerException", this.InnerException.ToException());
            }

            return exception;
        }
        #endregion

        #region Private Methods

        private static void TrimStackTrace(ServiceExceptionDetail exceptionDetail)
        {
            exceptionDetail.StackTrace = string.Empty;
            if (null != exceptionDetail.InnerException)
            {
                TrimStackTrace(exceptionDetail.InnerException);
            }
        }


        private static void SetExceptionField(Exception exception, string field, object value)
        {
            FieldInfo fieldInfo = typeof(Exception).GetField(field, BindingFlags.Instance | BindingFlags.NonPublic);
            fieldInfo.SetValue(exception, value);
        }

        #endregion
    }
}